package video

import (
	"bufio"
	"context"
	"fmt"
	"math"
	"os"
	"path/filepath"

	"gitee.com/mkwchecking/sr-admin/pkg/ffmpeg"
	"gitee.com/mkwchecking/sr-admin/pkg/ffmpeg/transcoder"
	"gitee.com/mkwchecking/sr-admin/pkg/file/image"
	"gitee.com/mkwchecking/sr-admin/pkg/file/utils"
)

// func NewHandler(repo IVideoRepo, opts *HandleOption) *VideoHandler {

// 	ffmpegCli := GetFFMpegCli(opts.FFMpegPath)

// 	return &VideoHandler{
// 		ffmpegCli:    ffmpeg.LocalFFMpeg(opts.FFMpegPath),
// 		probeCli:     ffmpeg.LocalFFprob(opts.FFProbePath),
// 		repo:         repo,
// 		opt:          opts,
// 		imageHandler: image.NewImageHandlerCustom(ffmpegCli),
// 	}
// }

func NewHandler(opts *HandleOption, ffmpegCli *ffmpeg.FFMpeg, probeCli *ffmpeg.FFProbe, repo IVideoRepo) *VideoHandler {
	return &VideoHandler{
		ffmpegCli:    ffmpegCli,
		probeCli:     probeCli,
		repo:         repo,
		opt:          opts,
		imageHandler: image.NewImageHandler(ffmpegCli),
	}
}

func GetFFMpegCli(path string) *ffmpeg.FFMpeg {
	if path == "" {
		path = ffmpeg.LookPathFFMpeg()
	}
	return ffmpeg.NewEncoder(path)
}

func GetFFProbeCli(path string) *ffmpeg.FFProbe {
	if path == "" {
		path = ffmpeg.LookPathFFProbe()
	}
	return ffmpeg.NewFFProbe(path)
}

type VideoHandler struct {
	ffmpegCli *ffmpeg.FFMpeg
	probeCli  *ffmpeg.FFProbe

	imageHandler *image.ImageHandler
	repo         IVideoRepo
	opt          *HandleOption
}

func (v *VideoHandler) OnHandleVideoFile(ctx context.Context, path string) error {
	var err error
	videoInfo, err := v.probeCli.NewVideoFile(path)
	if err != nil {
		return err
	}
	if prePath, err := v.repo.GetVideoSlicesGenPath(videoInfo); err == nil {
		if err = v.genVideoPreview(ctx, path, prePath, videoInfo); err != nil {
			return err
		}
	}

	if prePath, err := v.repo.GetVideoCoverGenPath(videoInfo); err == nil {
		if err = v.genVideoCover(ctx, path, prePath); err != nil {
			return err
		}
	}

	return err
}
func (v *VideoHandler) GenVideoCover(ctx context.Context, path, output string) error {
	return v.genVideoCover(ctx, path, output)
}

func (v *VideoHandler) genVideoCover(ctx context.Context, path, output string) error {
	return v.imageHandler.GenCoverToTarget(ctx, path, output)
}

func (v *VideoHandler) GenVideoPreview(ctx context.Context, path string, output string) error {
	fileInfo, err := v.probeCli.NewVideoFile(path)
	if err != nil {
		return err
	}

	return v.genVideoPreview(ctx, path, output, fileInfo)
}

func (v *VideoHandler) genVideoPreview(ctx context.Context, path string, output string, file *ffmpeg.VideoFile) error {
	if v.opt.PreviewSegments <= 1 {
		return v.genVideoChunk(ctx, path, output, &VideoChunkOpt{
			StartTime: 0,
			Duration:  file.FileDuration,
			Audio:     v.opt.PreviewAudio,
			Preset:    v.opt.PreviewPreset,
			Vsync2:    file.FrameRate <= 0.01,
		})
	} else {
		return v.handleVideoChunk(ctx, path, output, file)
	}
}

func (v *VideoHandler) handleVideoChunk(ctx context.Context, path string, output string, file *ffmpeg.VideoFile) error {
	tmpdir := output + "_temp"
	if err := os.MkdirAll(tmpdir, os.ModePerm); err != nil {
		return err
	}
	defer os.RemoveAll(tmpdir)

	step, offset := v.opt.getStepSizeAndOffset(file.FileDuration)

	segmentDuration := math.Max(v.opt.PreviewSegDuration, utils.MinSegmentDuration)

	// 缓存文件
	concatFile, err := os.CreateTemp(tmpdir, "*.txt")
	if err != nil {
		return err
	}
	concatWriter := bufio.NewWriter(concatFile)
	defer concatFile.Close()

	for i := 0; i < v.opt.PreviewSegments; i++ {
		ext := filepath.Ext(file.Path)
		chunkFile := filepath.Join(tmpdir, fmt.Sprintf("%03d%s", i, ext))
		chunkOpt := &VideoChunkOpt{
			StartTime: offset + float64(i)*step,
			Duration:  segmentDuration,
			Audio:     v.opt.PreviewAudio,
			Preset:    v.opt.PreviewPreset,
			Vsync2:    file.FrameRate <= 0.01,
		}

		if err := v.genVideoChunk(ctx, path, chunkFile, chunkOpt); err != nil {
			return err
		}
		if _, err := concatWriter.WriteString(fmt.Sprintf("file '%s'\n", filepath.Base(chunkFile))); err != nil {
			return fmt.Errorf("write concat file :%w", err)
		}
	}
	if err := concatWriter.Flush(); err != nil {
		return fmt.Errorf("flush concat file :%w", err)
	}

	// 合并切片
	sliceOpt := transcoder.SpliceOptions{
		OutputPath: output,
	}

	args := transcoder.Splice(concatFile.Name(), sliceOpt)
	return v.ffmpegRun(ctx, args)

}

// genVideoChunk 生成视频切片
// :path 视频路径
// :output 输出路径
// :opt 视频切片选项
// :return error
func (v *VideoHandler) genVideoChunk(ctx context.Context, path string, output string, opt *VideoChunkOpt) error {
	var filter ffmpeg.VideoFilter
	filter = filter.ScaleWidth(utils.ScenePreviewWidth)

	var arg ffmpeg.Args
	arg = arg.VideoFilter(filter)
	arg = append(arg,
		"-pix_fmt", "yuv420p",
		"-profile:v", "high",
		"-level", "4.2",
		"-preset", opt.Preset,
		"-crf", "21",
		"-threads", "4",
		"-strict", "-2",
	)

	trimOptions := transcoder.TranscodeOptions{
		OutputPath: output,
		StartTime:  opt.StartTime,
		Duration:   opt.Duration,

		XError:   true,
		SlowSeek: false,

		VideoCodec: ffmpeg.VideoCodecLibX264,
		VideoArgs:  arg,

		ExtraInputArgs:  v.opt.TranscodeInputArgs,
		ExtraOutputArgs: v.opt.TranscodeOutputArgs,
	}

	if opt.Vsync2 {
		trimOptions.VideoArgs = append(trimOptions.VideoArgs, "-vsync", "2")
	}

	if opt.Audio {
		var audioArg ffmpeg.Args
		audioArg = audioArg.AudioBitrate(utils.ScenePreviewAudioBitrate)

		trimOptions.AudioCodec = ffmpeg.AudioCodecAAC
		trimOptions.AudioArgs = audioArg
	}

	args := transcoder.Transcode(path, trimOptions)
	return v.ffmpegRun(ctx, args)
}

func (v *VideoHandler) ffmpegRun(ctx context.Context, arg ffmpeg.Args) error {
	cmd := v.ffmpegCli.Command(ctx, arg)
	if err := cmd.Start(); err != nil {
		return fmt.Errorf("ffmpeg Start error :%w", err)
	}

	if err := cmd.Wait(); err != nil {
		return fmt.Errorf("ffmpeg Wait error :%w", err)
	}
	return nil
}
